ReactのMain Concept
読んでみて
ふむsta.icon
コンポーネントツリーって感じの世界だな
stateはなるべく上位で定義して、propsで流し込む
関数を渡す、みたいなシチュも結構ある。setStateとか
render(), componentDidMount(), componentWillUnmount()
継承は使うな
-.icon
step1: UIをコンポーネントの階層構造に落とし込む
https://gyazo.com/e0b2351856d49ddab49e736487ed0477
囲んで、名前(コンポーネント名)付ける
step2: Reactで静的なバージョンを作成する
静的とはrender()のみから成るもの
stateは使わずpropsのみで仕上げる
step3: UI 状態を表現する必要かつ十分な state を決定する
扱うデータ列挙して
それぞれについてstateで扱うかどうか考える
1 元となる商品のリスト
not state
propsに渡されてくるので
2 ユーザが入力した検索文字列
state
3 チェックボックスの値
state
4 フィルタ済みの商品のリスト
not state
1と2と3から計算できるので
step4: state をどこに配置するべきなのかを明確にする
なるべく上位の(共通の)コンポーネントに配置して、propsで渡す(流す)感じにする
もし state を持つにふさわしいコンポーネントを見つけられなかった場合は、state を保持するためだけの新しいコンポーネントを作り、階層構造の中ですでに見つけておいた共通の親コンポーネントの上に配置する
新しいコンポーネントわざわざつくるのかsta.icon
step5: 逆方向のデータフローを追加する
これは何言ってるかわかんねsta.icon
チェックボックスを切り替えてみると、React がその入力を無視することがわかります。
しねえだろ
自動追従するんだろ?
あ、そうか、するわ。まだ静的だった。
それでも何言ってるかちょっと理解できないけど……sta.icon
理解した
FilterableProductTable ★2 state持ってるのはここ
SearchBar ★1 入力受け付けるのはここ
ProductTable
ProductCategoryRow
ProductRow
★2のstateを更新する契機が★1側にあるから、なんとかして★2に教えないといけない
どうするか?
★2が、★1に、コールバックを渡す
「おい、★1。てめえのinputを更新したらコイツを呼び出せやおら」
props.childrenで子要素にアクセスできる
code:jsx
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
// 子要素にアクセス
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
// 子要素とは DOM 要素
// この場合は、
// FancyBorder
// h1
// p
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
汎用的なコンポーネントにpropsを渡す形で書けば、特化コンポーネントをつくれる
code:jsx
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
// 汎用的なやつ
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
// 汎用的なやつを使ってるやつ
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}
aa
code:jsx
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
// ★2 ので、Dialog側では表示できる
// この場合、Dialogは「titleとmessageの後に表示する要素は、子要素として指定しろ」と言ってる
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
// 汎用的なDialogを使っている
// 子要素として input と button を定義してる ★1
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
継承は使わないでええで
Facebook では、何千というコンポーネントで React を使用していますが、コンポーネント継承による階層構造が推奨されるケースは全く見つかっていません。
非UIを再利用したい場合はモジュール化すればいい
コンポーネント間で非 UI 機能を再利用したい場合は、それを別の JavaScript モジュールに抽出することをお勧めします。コンポーネントはその関数やオブジェクト、クラスなどを継承することなくインポートすることで使用することができるでしょう。
before1
Calculator/
BoilingVerdict/
before2
Calculator/
TemperatureInput(scale="c")/
tempreture: string
TemperatureInput(scale="f")/
tempreture: string
これだとscale cの値とscale fの値が同期されない
原因は状態tempretureを各々がカプセル化してしまっていること
リフトアップ(親に持たせる)をすればよい
子では、stateではなくpropsで参照する(親から渡してもらう)ように変える
親では、リフトアップさせたstateを持つのと、変換処理の実装とそれをrender時に呼ばれるよう仕込むのと
---
after
Calculator/
tempreture: string
scale: string
TemperatureInput(scale="c", temperature={celsius}, onTemperatureChange={this.handleCelsiusChange})/
TemperatureInput(scale="f", temperature={fahrenheit}, onTemperatureChange={this.handleFahrenheitChange})/
と、書いたはいいが、かなりあっちこっち参照しててエグいsta.icon
そういうもん
state のリフトアップは双方向のバインディング (two-way binding) を行う方法より多くの “ボイラープレート” コードを生み出しますが、その効果としてバグを発見して切り出す作業が少なく済むようになります。
コード量が増える
でもバグは少なくなる(切り出し時に見る対象が狭くなる)
render内で、formのvalueをthis.stateにすることでSSOTにできる
<input type="text" value={this.state.value} onChange={this.handleChange} />
これを制御されたコンポーネントと呼ぶ
selectタグの場合、セレクトされたvalueだけ保持すればいい
<select value={this.state.value} onChange={this.handleChange}>
複数のinputを使い分けたい場合、inputにname属性を入れて、ハンドラ側ではevent.target.nameをチェック
動的プログラミングみたいなやり方で state.xxxx の xxxx を分ける
this.setState({ [name]: value});
以下に等しい
code:js
var partialState = {};
partialStatename = value; this.setState(partialState);
寄り道
制御されたコンポーネントでは value に this.state を入れていた
ReactのStateをSSOTにしている
が、非制御コンポーネントでは DOM を SSOT にする
そのためにちょっと特殊な書き方をする
ctorでcreateRef()
this.input = React.createRef();
render()ではrefを使う
<input type="text" ref={this.input} />
HTMLの文書内容を表現したもの
APIの一種
例
document.querySelectorAll("p");のdocument
window
JavaScriptがHTML文書の中身をいじるには、DOMを経由していじる
ブラウザがそうなっている
おさらい
code:map.js
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
各要素を、「その関数を適用した結果」に置き換えたものを返すsta.icon
mapで箇条書き書いてる例
code:mapの例.jsx
const listItems = numbers.map((numbers) =>
<li>{numbers}</li>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<ul>{listItems}</ul>);
numbersにmapを適用して、liの描画を足しているsta.icon
要素の集合を作成し中括弧 {} で囲むことで JSX に含めることができます。
1 要素の集合をJSXで普通につくる
2 1を{}で囲って呼び出す
key
code:これだとkey使え警告出る.jsx
// 常に props で受け取る
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
// 式なので {} で囲って指定しろ?
return (
<ul>{listItems}</ul>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
// 式なので {} で囲って指定しろ
root.render(<NumberList numbers={numbers} />);
code:key使う.jsx
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}> //★
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
Key は、どの要素が変更、追加もしくは削除されたのかを React が識別するのに役立ちます。
keyは一意な文字列使え
インデックスも使えないことはないが、並び順変わる場合に壊れるおそれがある
keyは兄弟要素間で一意であればよい。グローバルレベルで一意である必要はない
コンポーネントとして抽出する場合は、呼び出し元側でkeyを書く
<ListItem key={number.toString()} value={number} />
詳細
Reactは「更新された部分だけ描画する」が、内部的にはもちろん「更新された部分」を特定している
keyがない場合、全部スキャンしないとわからんが、keyがあれば「今から追加しようとしてるやつの有無」はO(1)でわかる(のでパフォーマンスよろしい)
ifdefみたいなことができる
code:jsx
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
三項演算子やcond && condがtrueならここが評価される表記を使った短縮も可能
が、読みにくいし使いたくねえなーsta.icon
コンポーネント中でreturn nullをすると、自分を非表示にできる
イベントはfuncNameを渡す
code:before.js
<button onclick="activateLasers()">
Activate Lasers
</button>
code:after.jsx
<button onClick={activateLasers}>
Activate Lasers
</button>
動作抑止はpreventDefault。return falseは使えない
code:jsx
function Form() {
function handleSubmit(e) {
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
addEventListenerの定義は要らない。render()の中で定義すればよい
render()内でthisを使う場合は、this問題に留意する必要がある code:ctorでbindするか.jsx
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make this work in the callback
this.handleClick = this.handleClick.bind(this); }
code:arrorを使うか.jsx
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures this is bound within handleClick
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
code:handleClickの中身、this使わずにクラスレベルだけ使うのでも良い.jsx
class LoggingButton extends React.Component {
// This syntax ensures this is bound within handleClick.
handleClick = () => {
console.log('this is:', this);
};
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
code:before.jsx
const root = ReactDOM.createRoot(document.getElementById('root'));
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
root.render(element);}
setInterval(tick, 1000);
code:こうしたい.jsx
root.render(<Clock />);
そのためにstateが必要
init, update, terminateといった概念が必要だから、かsta.icon
code:1 React.Componentを使え.jsx
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
code:2 Date情報を自分で持つ.jsx
class Clock extends React.Component {
// props は親に渡す(おまじない)
constructor(props) {
super(props);
// state は ctor で定義
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
// state を使うために this.state.xxxx という書き方に
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);
ライフサイクル
タイマーを設定したいのは、最初に Clock が DOM として描画されるときです。このことを React では “マウント (mounting)” と呼びます。
またタイマーをクリアしたいのは、Clock が生成した DOM が削除されるときです。このことを React では “アンマウント (unmounting)” と呼びます。
code:jsx
componentDidMount() { }
componentWillUnmount() { }
this.xxxx の xxxx を追加するのは自由
code:jsx
componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); }
train.iconなんかコピペすると改行死ぬし、JSXのformatterもネットにない……不便……
別に元データおかしくねえけどね
何やろね
俺のwin10がおかしい?
render() を再度実行させるためには、setState で更新する必要がある
code:jsx
tick() { this.setState({ date: new Date() }); }
setStateにはオブジェクトをセットする
非同期呼び出しにも対応するためらしい
code:ダメ.jsx
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
code:良い.jsx
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
普通のオブジェクトと関数オブジェクト、どっちもいけるってことかsta.icon
引数欲しい場合は後者を使う
code:arrowじゃなくても一応いける.jsx
// Correct
this.setState(function(state, props) {
return {
// あー、でも戻り値はオブジェクトだね
counter: state.counter + props.increment
};
});
stateのマージはshallow
this.state.xxxx だけ更新した場合、this.state.yyyy は更新されない
親は、子コンポーネントに、自分のstateを渡すことはできる
code:jsx
<FormattedDate date={this.state.date} />
これは親が、我が子FormattedDateに、this.state.date を渡している
子FormattedDateは、誰から渡されたのかは知らない
ダックタイピング的に、ただdateが渡されてるだけsta.icon
React要素(コンポーネント)の定義
code:これも有効。React要素を返す.jsx
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
code:これも同じ.jsx
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
ユーザー定義のコンポーネントはこうやって使う
code:jsx
const element = <Welcome name="Sara" />;
ただし大文字にしろ
補足: コンポーネント名は常に大文字で始めてください。
React は小文字で始まるコンポーネントを DOM タグとして扱います。例えば、<div /> は HTML の div タグを表しますが、<Welcome /> はコンポーネントを表しており、スコープ内に Welcome が存在する必要があります。
<welcome はダメ
たぶんHTMLタグと区別する方法が他にないからだろうsta.icon
props
React がユーザ定義のコンポーネントを見つけた場合、JSX に書かれている属性と子要素を単一のオブジェクトとしてこのコンポーネントに渡します。このオブジェクトのことを “props” と呼びます。
code:jsx
const element = <Welcome name="Sara" />;
^^^^^^^^^^^
この部分
{name: 'Sara'} に変換されて渡される
単一のAppコンポーネント
code:jsx
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
コンポーネントの細分化
なるべく再利用可能にすればいい。props.xxxx の xxxx も汎用的な名前にする
Avatar は、自身が Comment の内側でレンダーされているということを知っている必要はありません。なので props の名前として、author ではなく user というもっと一般的な名前を付けました。
コンポーネントが使用される文脈ではなく、コンポーネント自身からの観点で props の名前を付けることをお勧めします。
純粋
code:jsx
function sum(a, b) {
return a + b;
}
このような関数は入力されたものを変更しようとせず、同じ入力に対し同じ結果を返すので “純粋 (pure)” であると言われます。
副作用がないことsta.icon
Reactコンポーネントは純粋であるべし
全ての React コンポーネントは、自己の props に対して純関数のように振る舞わねばなりません。
パラダイムが違う
私達の経験上、時間の経過によりどのように UI が変更されるかを考えるよりも、任意の時点において UI がどのように見えるべきかを考えることで、あらゆる類のバグを排除することができます。
今の俺は全然ピンと来ないけどsta.icon
code:jsx
const element = <h1>Hello, world!</h1>;
これは正しい
jsの拡張構文で、マークアップも書けるようにしたもの
code:jsx
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<h1>Hello, world!</h1>);
rootにcreateRootする
renderは一回呼び出しておしまい